﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DataStuctureStudy.Stacks
{
    public static class Calculator
    {
        private static readonly Dictionary<string, int> _operators = new Dictionary<string, int>
        {
            { "+", 1 },
            { "-", 1 },
            { "*", 2 },
            { "/", 2 },
            { "%", 2 }
        };

        public static void Test(string expression)
        {
            var postfixTokens = GetPostfixTokens(GetTokens(expression));
            Console.WriteLine(expression + " = " + Calculate(postfixTokens));
        }

        private static int Calculate(IEnumerable<string> postfixTokens)
        {
            var stack = new Stack<int>();

            foreach (var item in postfixTokens)
            {
                if (IsOperator(item))
                {
                    switch (item)
                    {
                        case "+":
                            stack.Push(stack.Pop() + stack.Pop());
                            break;
                        case "-":
                            stack.Push(stack.Pop() - stack.Pop());
                            break;
                        case "*":
                            stack.Push(stack.Pop() * stack.Pop());
                            break;
                        case "/":
                            stack.Push(stack.Pop() / stack.Pop());
                            break;
                        case "%":
                            stack.Push(stack.Pop() % stack.Pop());
                            break;
                    }
                }
                else
                {
                    stack.Push(int.Parse(item));
                }
            }

            return stack.Pop();
        }

        private static IEnumerable<string> GetPostfixTokens(IEnumerable<string> infixTokens)
        {
            var postfixTokens = new List<string>();
            var stack = new Stack<string>();

            foreach (var token in infixTokens)
            {
                if (IsOperator(token))
                {
                    var curOperator = token;

                    if (stack.Count == 0)
                    {
                        stack.Push(curOperator);
                    }
                    else
                    {
                        while (stack.Count != 0)
                        {
                            var popOperator = stack.Pop();
                            if (popOperator == "(")
                            {
                                stack.Push(popOperator);
                                break;
                            }

                            if (_operators[curOperator] <= _operators[popOperator])
                            {
                                postfixTokens.Add(popOperator);
                            }
                            else
                            {
                                stack.Push(popOperator);
                                break;
                            }
                        }

                        stack.Push(curOperator);
                    }
                }
                else if (token == "(")
                {
                    stack.Push(token);
                }
                else if (token == ")")
                {
                    var popOperator = stack.Pop();
                    while (popOperator != "(")
                    {
                        postfixTokens.Add(popOperator);
                        popOperator = stack.Pop();
                    }
                }
                else
                {
                    postfixTokens.Add(token);
                }
            }

            while (stack.Count != 0)
            {
                postfixTokens.Add(stack.Pop());
            }

            return postfixTokens;
        }

        private static IEnumerable<string> GetTokens(string expression)
        {
            var tokens = new List<string>();
            var sb = new StringBuilder();

            foreach (var item in expression)
            {
                if (IsOperator(item.ToString()) || item == '(' || item == ')')
                {
                    if (sb.Length > 0)
                    {
                        tokens.Add(sb.ToString());
                        sb.Clear();
                    }

                    tokens.Add(item.ToString());
                }
                else
                {
                    sb.Append(item);
                }
            }

            return tokens;
        }

        private static bool IsOperator(string token)
        {
            return _operators.ContainsKey(token);
        }
    }
}
